/*
 * Controller class for the Conference Application
 * Authors: Daniel Henderson, Ching-Ting Huang, Jonathan Forbes
 * TCSS 360 (Group project) Spring 2013 
 * University of Washington
 */

package Model;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.ListIterator;
import java.util.Observable;

/**
 * Primary class that controls the functionality in the Conference Application
 * and acts as a decoupled bridge between the View and the various model classes 
 * that hold the data for the Users, Papers, Reviews, and Conferences.
 * 
 * @author Daniel Henderson
 * @author Ching-Ting Huang
 * @author Jonathan Forbes
 * @version 1.0
 */
public class Controller extends Observable{

	private boolean isDebugOn;

	private User current_user;

	private List<User> all_users_list;

	private List<Conference> all_conferences_list;

	private boolean userExistsFlag;

	private int paper_unique_counter;

	/**
	 * Primary Constructor for the Controller Class. This class controls
	 * the functionality in the program and acts as a decoupled bridge 
	 * between the View and the various model classes that hold the data
	 * for the Users, Papers, Reviews, and Conferences.
	 */
	public Controller() {
		isDebugOn = true;
		all_users_list = new ArrayList<User>();
		all_conferences_list = new ArrayList<Conference>();
		paper_unique_counter = 0;
		loadFile();
		if(isDebugOn) {
			for(User u : all_users_list) {
				System.out.println("User: " + u.getLogin() + " Pass: " + u.getPassword() + " Email: " + u.getEmail() + " # of Author Papers: " + u.getAuthorPapers().size() + " # of PC Papers: " + u.getPCPapers().size());
			}
		}
	}

	/**
	 * Method that will Serialize the List of Users and Conferences.
	 * @author Daniel Henderson 
	 */
	public void saveData() {   
		try {
			FileOutputStream userFileOut = new FileOutputStream("user_data.ser");
			ObjectOutputStream userOut = new ObjectOutputStream(userFileOut);
			userOut.writeObject(all_users_list);
			userOut.close();
			userFileOut.close();
		}
		catch(IOException i) {
			i.printStackTrace();
		}

		try {
			FileOutputStream conferenceFileOut = new FileOutputStream("conference_data.ser");
			ObjectOutputStream conferenceOut = new ObjectOutputStream(conferenceFileOut);
			conferenceOut.writeObject(all_conferences_list);
			conferenceOut.close();
			conferenceFileOut.close();
		}
		catch(IOException i) {
			i.printStackTrace();
		}
	}

	/**
	 * Method that will de-serialize the List of Users and Conferences and set them to the Controllers Lists.
	 * @author Daniel Henderson 
	 * @author Jonathan Forbes (edited to enable pointer reconstruction)
	 */
	@SuppressWarnings("unchecked")
	public void loadFile(){
		try {
			FileInputStream userFileIn = new FileInputStream("user_data.ser");
			ObjectInputStream userIn = new ObjectInputStream(userFileIn);
			all_users_list = (List<User>) userIn.readObject();
			userIn.close();
			userFileIn.close();
		}
		catch (IOException i) {//Problem loading file (Seed an admin account into the user List)
			User admin = new User("admin", "password", "email@yahoo.com", "Default Admin", true);
			all_users_list.add(admin);
			i.printStackTrace();
		}
		catch (ClassNotFoundException c) {
			c.printStackTrace();
		}

		try {
			FileInputStream conferenceFileIn = new FileInputStream("conference_data.ser");
			ObjectInputStream conferenceIn = new ObjectInputStream(conferenceFileIn);
			all_conferences_list = (List<Conference>) conferenceIn.readObject();
			conferenceIn.close();
			conferenceFileIn.close();
			/* Start recreating pointer connections
			   Make all Program Chair User objects the same between the two lists*/
			for (Conference c : all_conferences_list) {
				for (int i = 0; i < all_users_list.size(); i++) {
					if (all_users_list.get(i).getLogin().equals(c.getProgramChair().getLogin())) {
						all_users_list.set(i, c.getProgramChair());
						break;
					}
				}
			}
			// Make all Paper objects the same between the two lists
			for (Conference c : all_conferences_list) {
				for (Paper p : c.getConferencePapers()) {
					for (User u : all_users_list) {
						// check the author papers
						for (int i = 0; i < u.getAuthorPapers().size(); i++) {
							if (u.getAuthorPapers().get(i).getUniqueCode() == p.getUniqueCode()) {
								u.getAuthorPapers().set(i, p);
							}
						}
						// check the review papers
						for (int i = 0; i < u.getReviewPapers().size(); i++) {
							if (u.getReviewPapers().get(i).getUniqueCode() == p.getUniqueCode()) {
								u.getReviewPapers().set(i, p);
							}
						}
						// check the SPC papers
						for (int i = 0; i < u.getSPCPapers().size(); i++) {
							if (u.getSPCPapers().get(i).getUniqueCode() == p.getUniqueCode()) {
								u.getSPCPapers().set(i, p);
							}
						}
						// check the PC papers
						for (int i = 0; i < u.getPCPapers().size(); i++) {
							if (u.getPCPapers().get(i).getUniqueCode() == p.getUniqueCode()) {
								u.getPCPapers().set(i, p);
							}
						}
					}
					if (p.getUniqueCode() >= paper_unique_counter) {
						paper_unique_counter = p.getUniqueCode() + 1;
					}
				}
			}
		}
		catch (IOException i) {
			i.printStackTrace();
		}
		catch (ClassNotFoundException c) {
			c.printStackTrace();
		}
	}

	/**
	 * Sets current user logged into the system, then
	 * notify observers so the GUI can be updated with
	 * the new User information.
	 * 
	 * @param String loginName of the User
	 * @param String loginPassword of the User
	 * @return boolean Exists: true    else: false
	 * @author Daniel Henderson
	 */
	public boolean setCurrentUser(String loginName, String loginPassword) {
		userExistsFlag = false;
		ListIterator<User> userIterator = all_users_list.listIterator(); 
		while(userIterator.hasNext() && userExistsFlag == false) {
			User temp_user = (User) userIterator.next();
			if(temp_user.getLogin().equals(loginName) && temp_user.getPassword().equals(loginPassword)) { //login + password match
				current_user = temp_user;
				userExistsFlag = true;
			}
		}
		setChanged();
		notifyObservers(current_user);
		return userExistsFlag;
	}

	/**
	 * This method returns a List<User> of Users registered with the system. Does not include the
	 * default admin user account.
	 * 
	 * @return The List<User> of users, not including the default admin user.
	 * @author Jonathan Forbes
	 */
	public List<User> getUserList() {
		List<User> users;
		if(all_users_list.size() > 1) {
			users = all_users_list.subList(1, all_users_list.size());
		} else {
			users = new ArrayList<User>();
		}
		return users;
	}

	/**
	 * This method returns the admin account in the user list if it exists
	 * used for testing purposes and/or admin account recovery.
	 * 
	 * @return User for the default admin user which exists in index 0.
	 * @author Daniel Henderson
	 */
	public User getAdminUser() {
		User the_user;
		if(all_users_list.size() >= 1) {
			the_user = all_users_list.get(0);
		} else {
			the_user = null;
		}
		return the_user;
	}

	/**
	 * This method prints all reviewer email to a file and returns that file to any
	 * querying methods. The file created contains comma separated email addresses.
	 *
	 * @return the file containing all reviewer's email
	 * @author Ching-Ting Huang
	 */
	public File getReviewerEmailAddresses() {
		File file = new File("RevMail.txt");
		try {
			BufferedWriter bw = new BufferedWriter(new FileWriter(file));
			//FOR EACH paper in the SPC list look at the list of reviewer users and get their email address.
			for (int i = 0; i < current_user.getSPCPapers().size(); i++) {
				final List<User> rev = current_user.getSPCPapers().get(i).getReviewers();
				for (int j = 0; j < rev.size(); j++) {
					bw.write(rev.get(j).getEmail());
					bw.write(",");
					bw.newLine();
				}
			}
			bw.flush();
			bw.close();
		} catch(IOException e) {
			e.printStackTrace();
		}
		return file;
	}

	/**
	 * This method prints all subprogram chairs email to a file and return that file to 
	 * any querying methods. The file created contains comma separated email addresses.
	 * 
	 * @return a file containing all subprogram chair emails
	 * @author Ching-Ting Huang
	 */
	public File getSubProgramChairEmailAddresses() {
		File file = new File("SPCMail.txt");
		try {
			BufferedWriter bw = new BufferedWriter(new FileWriter(file));
			int flag = 0;
			// FOR EACH paper in the PC papers list, look at the SPC User and grab the email address.
			for (int i = 0; i < current_user.getPCPapers().size(); i++) {
				if(current_user.getPCPapers().get(i).getSPC() != null) {
					flag++;
					final String mail = current_user.getPCPapers().get(i).getSPC().getEmail();
					bw.write(mail);
					bw.write(",");
					bw.newLine();
				}
			}
			if(flag > 0) {
				bw.flush();
				bw.close();

			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return file;
	}

	/**
	 * Accept or Reject a Author's papers.
	 * 
	 * @param Paper the_paper the paper to assign to.
	 * @param int decision   Undecided:0   Accept:1   Reject:2.
	 * @author Jonathan Forbes
	 */
	public void approveRejectPaper(int decision, Paper the_paper) {
		the_paper.setAccepted(decision);
	}

	/**
	 * Assign a Reviewer User to a specific paper.
	 * 
	 * @param Paper the_paper the paper to assign to.
	 * @param User reviewer user that will be assigned to the paper.
	 * @author Jonathan Forbes
	 */
	public void assignReviewer(Paper the_paper, User reviewer) {
		the_paper.addReviewer(reviewer);
		reviewer.setPermission(1, true);
		reviewer.addReviewerPaper(the_paper);
		setChanged();
		notifyObservers(100);
	}

	/**
	 * Assign a SubProgram Chair to a specific paper.
	 * 
	 * @param Paper the_paper the paper to assign the SPC to.
	 * @param User SPC user that will be assigned to the paper.
	 * Daniel Henderson
	 */
	public void assignSPC(Paper the_paper, User SPC) {
		the_paper.setSPC(SPC);
		SPC.setPermission(2, true);
		SPC.addSPCPaper(the_paper);
		setChanged();
		notifyObservers(100);
	}

	/**
	 * This method attempts to replace the user settings of the current user with those passed.
	 * If the new username is unique, and not already registered on the list of users, then
	 * the method should succeed and return true, otherwise it will return false.
	 * 
	 * @param username The new username/login
	 * @param fullname The new full name of the user
	 * @param email The new email of the user
	 * @param password The new password of the user
	 * @return true if successful, false if the username is not unique
	 * @author Jonathan Forbes
	 */
	public boolean changeUserSettings(String username, String fullname, String email, String password) {
		boolean successfullyChanged = false;
		if(!username.isEmpty() && (uniqueUsername(username) || username.equals(current_user.getLogin()))) { //login not blank, and is either unique or the same as before
			if(!fullname.isEmpty()) { //not empty name
				if(!email.isEmpty() && emailContainsAt(email)) { //not empty email, and passes email check method
					if(!password.isEmpty() && checkPasswordSizeCorrect(password)) { //not empty password and passes password check method
						successfullyChanged = true;
						current_user.setLogin(username);
						current_user.setEmail(email);
						current_user.setFullname(fullname);
						current_user.setPassword(password);
						setChanged();
						notifyObservers(100);
					}
				}
			}
		}
		return successfullyChanged;		
	}


	/**
	 * Takes a String and checks it against all usernames in the user list. If it is unique
	 * (not counting unique case) then this method returns true.
	 * 
	 * @param username The String username to check for uniqueness
	 * @return true if not currently found in the user list, false otherwise
	 * @author Jonathan Forbes
	 */
	public boolean uniqueUsername(String username) {
		boolean isUnique = true;
		for(User temp : all_users_list) {
			if(username.equalsIgnoreCase(temp.getLogin())) {
				isUnique = false;
				break;
			}
		}
		return isUnique;
	}

	/**
	 * Takes a String and checks it against all conference names in the conference list. If it is unique
	 * (not counting unique case) then this method returns true.
	 * 
	 * @param conference name
	 * @return true if not currently found in the conference list, false otherwise
	 * @author Jonathan Forbes
	 * @author Daniel Henderson (edited Jonathan's uniqueUsername code to work with conferences)
	 */
	public boolean uniqueConferenceName(String conferencename) {
		boolean isUnique = true;
		for(Conference temp : all_conferences_list) {
			if(conferencename.equalsIgnoreCase(temp.getConferenceName())) {
				isUnique = false;
				break;
			}
		}
		return isUnique;
	}

	/**
	 * This method takes String and Date parameters and uses them to create a conference object
	 * (with Deadline date objects). This conference object is added to the List of Conferences
	 * that is kept in this Controller.
	 * 
	 * @param conference_name The title of the Conference
	 * @param conference_date The date of the Conference
	 * @param paper_submission The last day for paper submissions
	 * @param review_papers The last day for reviews to be done on Conference papers
	 * @param notify_authors The day paper authors will be notified of acceptance decisions
	 * @param paper_revision_submission The last day for revisions to be done to Conference papers
	 * @param program_chair The Conference program chair User object.
	 * @author Jonathan Forbes
	 */
	public void createConference(String conference_name, Date conference_date,
			Date paper_submission, Date review_papers, Date notify_authors,
			Date paper_revision_submission, User program_chair) {
		Calendar tempcal = Calendar.getInstance();
		tempcal.setTime(conference_date);
		Deadline conf_date = new Deadline(tempcal.get(Calendar.MONTH), tempcal.get(Calendar.YEAR), tempcal.get(Calendar.DAY_OF_MONTH));
		tempcal.setTime(paper_submission);
		Deadline paper_sub = new Deadline(tempcal.get(Calendar.MONTH), tempcal.get(Calendar.YEAR), tempcal.get(Calendar.DAY_OF_MONTH));
		tempcal.setTime(review_papers);
		Deadline rev_papers = new Deadline(tempcal.get(Calendar.MONTH), tempcal.get(Calendar.YEAR), tempcal.get(Calendar.DAY_OF_MONTH));
		tempcal.setTime(notify_authors);
		Deadline not_auth = new Deadline(tempcal.get(Calendar.MONTH), tempcal.get(Calendar.YEAR), tempcal.get(Calendar.DAY_OF_MONTH));
		tempcal.setTime(paper_revision_submission);
		Deadline pap_rev_sub = new Deadline(tempcal.get(Calendar.MONTH), tempcal.get(Calendar.YEAR), tempcal.get(Calendar.DAY_OF_MONTH));
		addConferenceToSystem(new Conference(conference_name, program_chair, conf_date, paper_sub, rev_papers, not_auth, pap_rev_sub));
	}

	/**
	 * Add a conference to the system after calling the helper
	 * method to check if another conference does not already exist
	 * with this name.
	 * 
	 * @param Conference the_conference 
	 * @author Jonathan Forbes
	 */
	public void addConferenceToSystem(Conference the_conference) {
		if(uniqueConferenceName(the_conference.getConferenceName())) {
			all_conferences_list.add(the_conference);
		}
	}
	
	/**
	 * Remove all conferences from the List. This is for testing purpose.
	 * 
	 * @param Conference the_conference 
	 * @author Jonathan Forbes
	 */
	public void removeConferencesFromSystem() {
				all_conferences_list.clear();

	}

	/**
	 * Deletes the paper from the Conference object, the PC User's list of papers,
	 * the SPC's list of papers, and each reviewer's list of papers (who have been reviewing the paper)
	 * 
	 * @param Paper the paper to be removed
	 * @author Jonathan Forbes
	 */
	public void deletePaper(Paper the_paper) {
		current_user.removePaper(the_paper);
		for (int i = 0; i < the_paper.getConference().getConferencePapers().size(); i++) {
			if (the_paper.getUniqueCode() == the_paper.getConference().getConferencePapers().get(i).getUniqueCode()) {
				System.out.println("Found the Conf Paper");
				the_paper.getConference().getConferencePapers().remove(i);
				break;
			}
		}
		for (int i = 0; i < the_paper.getConference().getProgramChair().getPCPapers().size(); i++) {
			if (the_paper.getUniqueCode() == the_paper.getConference().getProgramChair().getPCPapers().get(i).getUniqueCode()) {
				System.out.println("Found the PC Paper");
				the_paper.getConference().getProgramChair().getPCPapers().remove(i);
				break;
			}
		}
		if(the_paper.getSPC() != null) {
			for (int i = 0; i < the_paper.getSPC().getSPCPapers().size(); i++) {
				if (the_paper.getUniqueCode() == the_paper.getSPC().getSPCPapers().get(i).getUniqueCode()) {
					System.out.println("Found the SPC Paper");
					the_paper.getSPC().getSPCPapers().remove(i);
					break;
				}
			}
		}

		for (User u : the_paper.getReviewers()) {
			for (int i = 0; i < u.getReviewPapers().size(); i++) {
				if (the_paper.getUniqueCode() == u.getReviewPapers().get(i).getUniqueCode()) {
					System.out.println("Found a Reviewer Paper");
					u.getReviewPapers().remove(i);
					break;
				}
			}
		}
		setChanged();
		notifyObservers(100);
	}

	/**
	 * Call to the View and tell it to update because
	 * a user's permissions (roles) has changed. (Observer Pattern)
	 * 
	 * @author Daniel Henderson
	 */
	public void changedCurrentUserPermissions() {
		setChanged();
		notifyObservers(100);
	}

	/**
	 * Change the recommendation and rationale for the Paper. Used
	 * in the SPC class to make a recommendation on a paper.
	 * 
	 * @param Paper the_paper to modify
	 * @param int recommendation for the paper
	 * @param String rationale for the paper
	 * @author Jonathan Forbes
	 */
	public void modifyRecommendation(Paper the_paper, int recommendation, String rationale) {
		the_paper.setRecommendation(recommendation);
		the_paper.setRationale(rationale);
	}

	/**
	 * Create the review for the Paper.
	 * 
	 * @param Paper the_paper to modify
	 * @param int values for the various review radio button (10 int params total)
	 * @author Jonathan Forbes
	 */
	public void reviewPaper(Paper the_paper, int resp1, int resp2, int resp3, int resp4,
			int resp5, int resp6, int resp7, int resp8, int resp9, int resp10,
			String comments, User reviewer) {
		the_paper.addReview(resp1, resp2, resp3, resp4, resp5, resp6, resp7, resp8, resp9, resp10,
				comments, reviewer);
	}

	/**
	 * Replace old review made by the same reviewer with a newer version of their review
	 * for the paper.
	 * 
	 * @param the_paper is the paper being reviewed.
	 * @param int values for the varies review radio buttons (10 int params).
	 * @param index is the placement of the old review in the list of reviews for the paper.
	 * @author Ching-Ting Huang
	 */

	public void replaceReview(Paper the_paper, int resp1, int resp2, int resp3, int resp4,
			int resp5, int resp6, int resp7, int resp8, int resp9, int resp10,
			String comments, User reviewer, int index) {
		the_paper.replaceReview(resp1, resp2, resp3, resp4, resp5, resp6, resp7, resp8, resp9, resp10,
				comments, reviewer, index);
	}

	/**
	 * This method will take a Paper object as a parameter
	 * along with the Conference, and add that Paper to the
	 * conference.
	 * 
	 * @param Conference The conference to add the paper to
	 * @param Paper The paper to add to the conference
	 * @author Jonathan Forbes
	 */
	public void submitPaper(Paper thePaper, Conference theConference) {
		thePaper.setUniqueCode(paper_unique_counter);
		paper_unique_counter++;
		current_user.addPaper(thePaper);
		theConference.addPaper(thePaper);
		setChanged();
		notifyObservers(thePaper);
	}

	/**
	 * Method that add a User to system after checking if the Username is unique and does not exist.
	 * 
	 * @param User to be added to system
	 * @return boolean if Unique: true   Not Unique: false
	 * @author Daniel Henderson 
	 */
	public boolean addUserToSystem(User new_user) {
		boolean success = false;
		if(uniqueUsername(new_user.getLogin())) {
			all_users_list.add(new_user);
			success = true;
		}
		return success;
	}

	/**
	 * Method that compares two String to ensure they are the same.
	 * Used for login Window.
	 * 
	 * @param String Password to be checked against password 2
	 * @param String Password to be checked against password 1
	 * @return Boolean answer to whether passwords are the same.
	 * @author Daniel Henderson 
	 */
	public boolean checkPasswordSame(String pw1, String pw2) {
		boolean answer = false;
		if(pw1.contentEquals(pw2)) {
			answer = true;
		}
		return answer;
	}

	/**
	 * Method that checks a string and ensures it contains an "AT" symbol
	 * a period, and at least 5 characters in the string.
	 * Used for registration Window to check the email.
	 * 
	 * @param String email address to be checked
	 * @return Boolean answer to whether the email does in fact contain the symbol
	 * @author Daniel Henderson 
	 */
	public boolean emailContainsAt(String emailAdress) {
		boolean answer = false;
		if(emailAdress.contains("@") && 
				emailAdress.length() >= 5 &&
				emailAdress.contains(".")) {
			answer = true;
		}
		return answer;
	}

	/**
	 * Method that checks a String to ensure it is the appropriate length >= 8
	 * and that no spaces appear.
	 * 
	 * @param String Password to be checked against password 2
	 * @return Boolean answer to whether the password is sufficient length.
	 * @author Daniel Henderson 
	 */
	public boolean checkPasswordSizeCorrect(String password) {
		boolean answer = false;
		if(password.length() >= 8) {
			answer = true;
		}
		if(password.contains(" ")) { //Check for spaces in password
			answer = false;
		}
		return answer;

	}

	/**
	 * Getter method to see if a user exists, return true, else return false.
	 * @return boolean answer to whether the User does infact exist in the system.
	 * @author Daniel Henderson
	 */
	public boolean getUserExists() {
		return userExistsFlag;
	} 

	/**
	 * Setter method to set if user exists.
	 * @param boolean answer to set if user exists in the system.
	 * @author Daniel Henderson
	 */
	public void setUserExists(boolean answer) {
		userExistsFlag = answer;
	}

	/**
	 * Getter method to return permissions (roles) for the current User.
	 * @return boolean [] of permissions for the current user.
	 * @author Jonathan Forbes
	 */
	public boolean[] getPermissions() {
		return current_user.getPermissions();
	}

	/**
	 * Getter method to get a List of Conferences.
	 * @return List<Conference> the conferences.
	 * @author Jonathan Forbes
	 */
	public List<Conference> getConferenceList() {
		return all_conferences_list;
	}

	/**
	 * Getter method used for JUNIT testing to get the actual user list size.
	 * @return int actual user count in system including the admin account.
	 * @author Jonathan Forbes
	 */
	public int getUserListActualSize() {
		return all_users_list.size();
	}

	/**
	 * Getter method to get the currents users login.
	 * @return String login of the current user.
	 * @author Jonathan Forbes
	 */
	public String getCurrentUsername() {
		return current_user.getLogin();
	}

	/**
	 * Getter method to return the current logged in User.
	 * @return User currently logged into the system.
	 * @author Jonathan Forbes
	 */
	public User getCurrentUser() {
		return current_user;
	}

	/**
	 * Getter method to get Peer Reviews Papers for the currently logged in User.
	 * @return List<Paper> of the review papers for this user.
	 * @author Jonathan Forbes
	 */
	public List<Paper> getPeerReviewPapers() {
		return current_user.getReviewPapers();
	}

	/**
	 * Getter method to get Program Chair Papers for the currently logged in User.
	 * @return List<Paper> of the PC papers for this user.
	 * @author Jonathan Forbes
	 */
	public List<Paper> getPCPapers() {
		return current_user.getPCPapers();
	}

	/**
	 * Getter method to get SubProgra Chair Papers for the currently logged in User.
	 * @return List<Paper> of the SPC papers for this user.
	 * @author Jonathan Forbes
	 */
	public List<Paper> getSPCPapers() {
		return current_user.getSPCPapers();
	}

	/**
	 * Getter method to get Author Papers for the current logged in User.
	 * @return List<Paper> of the Author papers for this user.
	 * @author Jonathan Forbes
	 */
	public List<Paper> getUserSubmittedPapers() {
		return current_user.getAuthorPapers();
	}
}

































